package net.pimpas.security.jaas.serviceprovider;

import java.io.IOException;

import net.pimpas.core.configuration.exception.ConfigurationException;
import net.pimpas.core.serviceprovider.ServiceProviderAnnotation;
import net.pimpas.core.serviceprovider.instancetype.ServiceProviderInstanceType;
import net.pimpas.logging.serviceprovider.LoggingServiceProvider;
import net.pimpas.logging.serviceprovider.LoggingServiceProviderFactoryImpl;
import net.pimpas.security.jaas.serviceprovider.auth.JAASSubject;
import net.pimpas.security.jaas.serviceprovider.auth.login.JAASLoginContext;
import net.pimpas.security.jaas.serviceprovider.auth.login.callback.JAASCallback;
import net.pimpas.security.jaas.serviceprovider.auth.login.callback.JAASCallbackHandler;
import net.pimpas.security.jaas.serviceprovider.auth.login.callback.name.JAASNameCallback;
import net.pimpas.security.jaas.serviceprovider.auth.login.callback.password.JAASPasswordCallback;
import net.pimpas.security.jaas.serviceprovider.auth.login.principal.JAASPrincipal;
import net.pimpas.security.jaas.serviceprovider.auth.login.principal.JAASPrincipalImpl;
import net.pimpas.security.serviceprovider.SecurityServiceProvider;
import net.pimpas.security.serviceprovider.auth.login.LoginContext;
import net.pimpas.security.serviceprovider.auth.login.LoginException;
import net.pimpas.security.serviceprovider.auth.login.LoginStatus;
import net.pimpas.security.serviceprovider.auth.login.callback.Callback;
import net.pimpas.security.serviceprovider.auth.login.callback.CallbackListener;
import net.pimpas.security.serviceprovider.auth.login.callback.UnsupportedCallbackException;

@ServiceProviderAnnotation(
		instanceType=ServiceProviderInstanceType.NORMAL,
		serviceProviderType=SecurityServiceProvider.class
)
public class JAASSecurityServiceProviderImpl implements JAASSecurityServiceProvider {
	
	private static final LoggingServiceProvider logger = 
		LoggingServiceProviderFactoryImpl.getLoggingServiceProvider( JAASSecurityServiceProviderImpl.class );
	
	private JAASCallbackHandler handler;
	private JAASSubject subject;
	private String name;
	private LoginStatus status;

	public LoginStatus authenticate(String name, JAASCallbackHandler callbackHandler) throws LoginException {
		this.setHandler(callbackHandler);
		this.setName(name);
		this.setStatus(null);
		
		return this.authenticate();
	}
	
	private LoginStatus authenticate() throws LoginException {
		try {
			logger.info("[AUTHENTICATE]: Executing Authenticate for ["+this.getName()+"]");
			if(this.getSubject() == null)
				this.setSubject(new JAASSubject());
			LoginContext lc = new JAASLoginContext(this.getName(), this.getSubject(), this.getHandler());
			lc.login();
		} catch(LoginException e) {
			this.setStatus(LoginStatus.ERROR);
			logger.error("[AUTHENTICATE]: Error in ["+this.getName()+"]", e);
			throw e;
		} catch (javax.security.auth.login.LoginException e) {
			this.setStatus(LoginStatus.ERROR);
			logger.error("[AUTHENTICATE]: Error in ["+this.getName()+"]", e);
			throw new LoginException(e.getCause());
		}
		
		this.setStatus(LoginStatus.SUCCESS);
		logger.info("[AUTHENTICATE]: Authentication successfull for ["+this.getName()+"]");
		return this.getStatus();
	}

	public LoginStatus authenticate(String name, JAASCallbackHandler callbackHandler, JAASSubject subject) throws LoginException {
		this.setHandler(callbackHandler);
		this.setName(name);
		
		return this.authenticate();
	}


	public void config() throws ConfigurationException {
		
	}

	public JAASCallbackHandler getHandler() {
		return handler;
	}

	public void setHandler(JAASCallbackHandler handler) {
		this.handler = handler;
	}

	public JAASSubject getSubject() {
		return subject;
	}

	public void setSubject(JAASSubject subject) {
		this.subject = subject;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public LoginStatus getStatus() {
		return status;
	}

	public void setStatus(LoginStatus status) {
		this.status = status;
	}

	public JAASCallback createCallback(String name, Object... params) {
		// TODO later
		return null;
	}

	public JAASNameCallback createNameCallback(String prompt) {
		return new JAASNameCallback(prompt);
	}

	public JAASNameCallback createNameCallback(String prompt, String defaultName) {
		return new JAASNameCallback(prompt, defaultName);
	}

	public JAASPasswordCallback createPasswordCallback(String prompt, boolean echoOn) {
		return new JAASPasswordCallback(prompt, echoOn);
	}

	public JAASCallbackHandler createCallbackHandler(final Runnable action) {
		return new JAASCallbackHandler() {
		
			@Override
			public void handle(final Callback... callbacks) throws IOException,
					UnsupportedCallbackException {
				action.run();
			}
		};
	}

	public LoginStatus authenticate(String name, final CallbackListener listener) throws LoginException {
		JAASCallbackHandler handler = new JAASCallbackHandler() {
			
			@SuppressWarnings("unchecked")
			@Override
			public void handle(Callback... callbacks) throws IOException,
					UnsupportedCallbackException {
				for(int i=0;i<callbacks.length;i++)
					listener.processCallback(callbacks[i]);
			}
		
		};
		
		return this.authenticate(name, handler);
	}

	public LoginStatus authenticate(String name, final CallbackListener listener, JAASSubject sub) throws LoginException {
		this.setHandler(new JAASCallbackHandler() {
			
			@SuppressWarnings("unchecked")
			@Override
			public void handle(Callback... callbacks) throws IOException,
					UnsupportedCallbackException {
				for(int i=0;i<callbacks.length;i++)
					listener.processCallback(callbacks[i]);
			}
		});
		
		return this.authenticate(name, this.getHandler(), sub);
	}

	public JAASPrincipal createPrincipal(String name, Object ob) {
		return new JAASPrincipalImpl(name, ob);
	}

	public LoginStatus unAuthenticate(String name, final CallbackListener listener, JAASSubject sub) throws LoginException {
		JAASCallbackHandler handler = new JAASCallbackHandler() {
			
			@SuppressWarnings("unchecked")
			@Override
			public void handle(Callback... callbacks) throws IOException,
					UnsupportedCallbackException {
				for(int i=0;i<callbacks.length;i++)
					listener.processCallback(callbacks[i]);
			}
		
		};
		
		this.setSubject(sub);
		return this.unAuthenticate(name, handler);
	}

	public LoginStatus unAthenticate(String name, JAASCallbackHandler handler, JAASSubject sub) throws LoginException {
		this.setHandler(handler);
		this.setName(name);
		this.setSubject(sub);
		
		return this.unAuthenticate();
	}

	public LoginStatus unAuthenticate(String name, JAASCallbackHandler handler) throws LoginException {
		this.setHandler(handler);
		this.setName(name);
		
		return this.unAuthenticate();
	}
	
	private LoginStatus unAuthenticate( ) throws LoginException {
		try {
			logger.info("[UNAUTHENTICATE]: Executing UnAuthentication for ["+this.getName()+"]");
			if(this.getSubject() == null)
				this.setSubject(new JAASSubject());
			LoginContext lc = new JAASLoginContext(this.getName(), this.getSubject(), this.getHandler());
			lc.logout();
		} catch(LoginException e) {
			this.setStatus(LoginStatus.ERROR);
			logger.error("[UNAUTHENTICATE]: Error in ["+this.getName()+"]", e);
			throw e;
		} catch(javax.security.auth.login.LoginException e) {
			this.setStatus(LoginStatus.ERROR);
			logger.error("[UNAUTHENTICATE]: Error in ["+this.getName()+"]", e);
			throw new LoginException(e.getCause());
			
		}
		
		this.setStatus(LoginStatus.SUCCESS);
		logger.info("[UNAUTHENTICATE]: UnAuthentication successfull for ["+this.getName()+"]");
		return this.getStatus();
	}

	public LoginStatus unAuthenticate(String name, final CallbackListener listener) throws LoginException {
		JAASCallbackHandler handler = new JAASCallbackHandler() {
			
			@SuppressWarnings("unchecked")
			@Override
			public void handle(Callback... callbacks) throws IOException,
					UnsupportedCallbackException {
				for(int i=0;i<callbacks.length;i++)
					listener.processCallback(callbacks[i]);
			}
		};
		
		return this.unAuthenticate(name, handler);
	}
	
	public boolean isTerminated() {
		return false;
	}
}